Spring Cloud—九、服务网关Spring Cloud Zuul

Author Avatar
zuoqy 12月 03, 2018
  • 在其它设备中阅读本文章

9.1、分析

通过前面的学习,使用Spring Cloud实现微服务的架构基本成型,大致是这样的:微服务架构.png

我们使用Spring Cloud netflix中的Eureka实现了服务注册中心以及服务注册与发现;而服务间通过Ribbon或Feign实现服务的消费以及均衡负载;通过Spring Cloud Config实现了应用多环境的外部化配置以及版本管理。为了使得服务集群更为健壮,使用Hystrix的融断机制来避免在微服务架构中个别服务出现异常时引起的故障蔓延。

在该架构中,我们的服务集群包含:内部服务Service A和Service B,他们都会注册与订阅服务至Eureka Server,而Open Service是对外的服务,通过均衡负载公开至服务调用方。我们把焦点聚集在对外服务这块,这样的实现是否合理,或者是否有更好的实现方式呢?

先来说说这样结构需要做的一些事儿以及存在的不足:
1.首先,破坏了服务无状态特点。

  • a.为了保证对外服务的安全性,我们需要实现对服务访问的权限控制,而开放服务的权限控制机制将会贯穿并污染整个开放服务的业务逻辑,这会带来的最直接问题是,破坏了服务集群中Rest Api无状态的特点。
    • b.从具体开发测试的角度来说,在工作中出了要考虑实际的业务逻辑之外,还需要额外可续对接口访问的控制处理。

2.其次,无法直接复用既有接口。

  • a.当我们需对一个既有的集群内访问接口,实现外部服务访问时,我们不得不通过在原有接口上增加校验逻辑,或增加一个代理调用来实现权限控制,无法直接复用原有的接口。

面对类似上面的问题,我们要如何解决呢?答案是:服务网关!

为了解决上面这些问题,我们需要将权限控制这样的东西从我们的服务单元中抽离出去,而最适合这些逻辑的地方就是处于对外访问最前端的地方,我们需要一个更强大一些的负载均衡器->服务网关。

服务网关时微服务结构中一个不可或缺的部分。通过服务网关统一向外系统提供REST API的过程中,除了具备服务路由、均衡负载功能之外,它还具备了权限控制等功能。Spring Cloud Netflix中的Zuul就担任了这样的一个角色,为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。

9.2、Zuul的简介

官网:https://github.com/Netflix/zuul
zuul.png
Zuul是Netflix开源的微服务网关,它可以和Eureka、Ribbon、Hystrix等组件配合使用。

Zuul的核心是一系列的过滤器,这些过滤器可以完成以下功能。

  • 身份认证与安全:识别每个资源的验证要求,并拒绝那些与要求不符的请求。
  • 审查与监控:在边缘位置追踪有意义的数据和统计结果,从而带来精确的生产试图。
  • 动态路由:动态地将请求路由到不同的后端集群。
  • 压力测试:逐渐增加指向集群的流量,以了解性能。
  • 负载分配:为每一种负载类型分配对应容量,并弃用超出限定值的请求。
  • 静态响应处理:在边缘位置直接建立部分响应,从而避免其转发到内部集群。
  • 多区域弹性:跨越AWS Region进行请求路由,旨在实现ELB(Elastic Load Balancing)使用的多样化,以及让系统的边缘更贴近系统的使用者。

Spring Cloud对Zuul进行了整合与增强。目前,Zuul使用的默认HTTP客户端是Apache HTTP Client,也可以使用RestClient或者okhttp3.0kHttpClient。如果想要使用RestClient,可以设置ribbon.restclient.enabled=true;想要使用okhttp3.0kHttpClient,可以设置ribbon.okhttp.enabled=true。

9.3、使用Zuul之后的架构

架构.png
从架构图中可以看出,客户端请求微服务时,先经过Zuul之后再请求,这样就可以将一些类似于校验的业务逻辑放到zuul中完成。

而微服务自身只需要关注自己的业务逻辑即可。

9.4、快速入门

9.4.1、创建工程springcloud-demo-zuul
9.4.2、导入依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--导入Spring Cloud的依赖管理-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
9.4.3、在启动类上添加注解@EnableZuulProxy

添加注解.png

9.4.4、编写application.properties文件
1
2
server.port=8080
spring.application.name=springcloud-demo-zuul
9.4.5、编写路由规则

首先,查看Eureka中的服务:Eureka.png
可以看到,当前Eureka中有2个商品服务。

接下来,我们编写路由规则:

1
2
3
4
 #配置请求Url的请求规则
zuul.routes.springcloud-demo-item.path=/springcloud-demo-item/**
#真正的微服务地址
zuul.routes.springcloud-demo-item.url=http://127.0.0.1:18100

9.4.6、启动测试

测试.png
可以看到,已经通过zuul访问到了商品服务。

9.5、面向服务的路由

在快速入门中我们已经通过了URL映射,访问到了商品微服务。这样做会存在一个问题,就是,如果商品微服务的地址发生了改变怎么办?
很自然的能够想到,不应该配置具体的url而是走Eureka注册中心获取地址。

9.5.1、添加Eureka服务的依赖
1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</exclusion>
</exclusions>
</dependency>
9.5.2、修改application.properties配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 server.port=8080
spring.application.name=springcloud-demo-zuul
#配置请求Url的请求规则
zuul.routes.springcloud-demo-item.path=/springcloud-demo-item/**
#真正的微服务地址
#zuul.routes.springcloud-demo-item.url=[http://127.0.0.1:18100](http://127.0.0.1:18100/)
#指定Eureka注册中心的服务id
zuul.routes.springcloud-demo-item.service-id=springcloud-demo-item
#是否将自己注册到Eureka服务中
eureka.client.register-with-eureka=false
#是否从Eureka中获取信息
eureka.client.fetch-registry=true
#Eureka服务端与Eureka客户端交互地址
eureka.client.serviceUrl.defaultZone=http://zuo:123456@127.0.0.1:8888/eureka/
#将自己得ip地址注册到Eureka服务中
eureka.instance.prefer-ip-address=true
9.5.3、在启动类中增加@EnableDiscoveryClient注解

启动类.png

9.5.4、重启测试

查看Eureka注册中心:
eureka.png
发现已经有springcloud-demo-zuul在注册中心了。

接下来测试,功能是否正常:
测试.png
发现一切正常

9.6、zuul配置详解

9.6.1、指定服务id

配置zuul.routes.指定微服务的serviceId=指定路径即可。例如:
zuul.routes.springcloud-demo-item=/item/**
这样设置,springcloud-demo-item微服务就会被映射到/item/**

9.6.2、忽略指定服务

忽略服务非常简单,可以使用zuul.ignored-service配置需要忽略的服务,多个逗号分割。例如:
zuul.ignored-services=springcloud-demo-item,springcloud-demo-a
这样就可让Zuul忽略sptringcloud-demo-item和springcloud-demo-a微服务,只代理其他微服务。

9.6.3、忽略所有服务,只是有路由指定

很多场景下,可能只想要让Zuul代理指定的微服务,此时可以将zuul.ignored-services设为.
`zuul.ignored-services=
zuul.routes.springcloud-demo-item=/item/**`

这样就可以让Zuul只路由springcloud-demo-item微服务

9.6.4、同时配置path和url
1
2
3
4
#配置请求Url的请求规则
zuul.routes.springcloud-demo-item.path=/springcloud-demo-item/**
#真正的微服务地址
zuul.routes.springcloud-demo-item.url=http://127.0.0.1:18100

其中springcloud-demo-item可以任意起名,只是给路由一个名称。
这样就可以将/springcloud-demo-item/映射到http://127.0.0.1:18100/

需要注意的是,使用这种方式配置的路由不会作为HystrixCommand执行,同时也不能使用Ribbon来负载均衡多个URL。

9.6.5、使用正则表达式指定路由规则

正则.png

9.6.6、路由前缀

路由前缀.png

9.6.7、忽略某些路径

忽略某些路径.png

9.7、过滤器

过滤器是zuul的重要组件

9.7.1、过滤器ZuulFilter

过滤器.png
zuulFilter是一个抽象类,其实现类需要实现4个方法:
1.shouldFilter:返回一个Boolean值,判断该过滤器是否需要执行。返回true执行,返回false不执行。
2.run:过滤器的具体业务逻辑。
3.filterType:返回字符串代表过滤器的类型
    a) pre:请求在被路由之前执行
    b) routing:在路由请求时调用
    c) post:在routing和error过滤器之后调用
    d) errror:处理请求时发生错误调用
4.filterOrder:通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高。

9.7.2、执行过程

执行过程.png
从上图中我们可以看到,当外部HTTP请求到达API网关服务的时候,首先它会进入第一个阶段pre,在这里它会被pre类型的过滤器进行处理,该类型过滤器的主要目的是在进行请求路由之前做一些前置加工,比如请求的校验等。在完成了pre类型的过滤器处理之后,请求进入第二个阶段routing,也就是之前说的路由请求转发阶段,请求将会被routing类型过滤器处理。这里的具体处理内容就是将外部请求转发到具体服务实例上去的过程,当服务实例将请求结果都返回之后,routing阶段完成,请求进入第三个阶段post。此时请求将会被post类型的过滤器处理,这些过滤器在处理的时候不仅可以获取到请求信息,还能获取到服务实例的返回信息,所以在post类型的过滤器中,我们可以对处理结果进行一些加工或转换等内容。另外,还有一个特殊的阶段error,该阶段只有在上述三个阶段中发生异常的时候才会触发,但是它的最后流向还是post类型的过滤器,因为它需要通过post过滤器将最终结果返回给请求客户端(对于error过滤器的处理,在Spring Cloud Zuul的过滤链中实际上有一些不同。)

9.8、过滤器实战

需求:通过编写过滤器实现用户是否登陆的检查。
实现:通过判断请求中是否有token,如果有任务就是已经登陆的,如果没有就认为是非法请求,响应401。

9.8.1、编写UserLoginZuulFilter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package cn.zuoqy.springclouddemozuul.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;

/**
* 登陆验证过滤器
* Created by zuoqy on 13:48 2018/10/27.
*/
@Component // 加入到Spring容器
public class UserLoginZuulFilter extends ZuulFilter {

    @Override
    public String filterType() {
        return "pre"; // 设置过滤类型为:pre
    }

    @Override
    public int filterOrder() {
        return 0; // 设置执行顺序
    }

    @Override
    public boolean shouldFilter() {
        return true; // 该过滤器需要执行
    }

    @Override
    public Object run() { // 编写业务逻辑

        RequestContext context = RequestContext.getCurrentContext();
        HttpServletRequest req = context.getRequest();
        String token = req.getParameter("token");

        if (StringUtils.isEmpty(token)) {
            // 过滤该请求,不对其进行路由
            context.setSendZuulResponse(false);
            // 设置响应状态
            context.setResponseStatusCode(401);
        }
        return null;
    }

}
9.8.2、启动测试

测试1.png
测试2.png
可以看到过滤器已经生效。


出自:zuoqy博客
如若转载请注明出处!